package com.juyu.miliao.authentication.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.juyu.miliao.authentication.security.*;
import com.juyu.miliao.authentication.service.UserService;
import com.juyu.miliao.common.security.JsonAuthFailureHandler;
import com.juyu.miliao.common.security.JsonAuthSuccessHandler;
import com.juyu.miliao.common.security.JsonAuthenticationEntryPoint;
import com.juyu.miliao.common.security.JsonInvalidSessionStrategy;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.session.InvalidSessionStrategy;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

import static com.juyu.miliao.authentication.controller.ExceptionHandlers.NO_LOGIN;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	@Autowired
	private ObjectMapper objectMapper;
    @Autowired
	private UserService userService;
	@Autowired
	private AuthenticationManager authenticationManager;

	@Autowired
	private AmqpTemplate amqpTemplate;

	@Override
	public void configure(WebSecurity web) throws Exception {
		web.ignoring().antMatchers("/verificationCode/**", "/h2-console");
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.addFilterAt(verifyUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
				.addFilterAfter(phoneNumberRegisterFilter(), UsernamePasswordAuthenticationFilter.class)
				.addFilterAfter(ThirdPartyLoginFilter(), UsernamePasswordAuthenticationFilter.class)
				.addFilterAfter(automaticLogonRegisterFilter(), UsernamePasswordAuthenticationFilter.class)
				.addFilterAfter(verifyUsernameVerificationCodeAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
				.authorizeRequests()
				.antMatchers("/login/username", "/logout", "/register/phoneNumber", "/login/thirdParty",
						 "/login/automatic","/login/verificationCode","/verificationCode/phoneNumForLogin"
                )
				.permitAll().anyRequest().authenticated().and().csrf().disable().logout().clearAuthentication(true)
				.logoutUrl("/logout").logoutSuccessUrl("/login").and().exceptionHandling()
				.authenticationEntryPoint(authenticationEntryPoint()).and().sessionManagement()
				.invalidSessionStrategy(invalidSessionStrategy())
				.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED).sessionFixation().none()
				.and()
				.cors().configurationSource(corsConfigurationSource());
	}

	@Bean
	public VerifyUsernamePasswordAuthenticationFilter verifyUsernamePasswordAuthenticationFilter() {
		VerifyUsernamePasswordAuthenticationFilter filter = new VerifyUsernamePasswordAuthenticationFilter();
		filter.setAuthenticationSuccessHandler(authSuccessHandler());
		filter.setAuthenticationFailureHandler(authFailureHandler());
		filter.setFilterProcessesUrl("/login/username");
		filter.setAuthenticationManager(authenticationManager);
		return filter;
	}
	@Bean
	public VerifyUsernameVerificationCodeAuthenticationFilter verifyUsernameVerificationCodeAuthenticationFilter() {
		VerifyUsernameVerificationCodeAuthenticationFilter filter = new VerifyUsernameVerificationCodeAuthenticationFilter();
		filter.setAuthenticationSuccessHandler(authSuccessHandler());
		filter.setAuthenticationFailureHandler(authFailureHandler());
		filter.setFilterProcessesUrl("/login/verificationCode");
		filter.setAuthenticationManager(authenticationManager);
		return filter;
	}

	@Bean
	public AutomaticLogonRegisterFilter automaticLogonRegisterFilter() {
		AutomaticLogonRegisterFilter filter = new AutomaticLogonRegisterFilter();
		filter.setAuthenticationSuccessHandler(authSuccessHandler());
		filter.setAuthenticationFailureHandler(authFailureHandler());
		filter.setFilterProcessesUrl("/login/automatic");
		filter.setAuthenticationManager(authenticationManager);
		return filter;
	}

	@Bean
	public PhoneNumberRegisterFilter phoneNumberRegisterFilter() {
		PhoneNumberRegisterFilter filter = new PhoneNumberRegisterFilter();
		filter.setAuthenticationSuccessHandler(authSuccessHandler());
		filter.setAuthenticationFailureHandler(authFailureHandler());
		filter.setFilterProcessesUrl("/register/phoneNumber");
		filter.setAuthenticationManager(authenticationManager);
		return filter;
	}

	@Bean
	public ThirdPartyLoginFilter ThirdPartyLoginFilter() {
		ThirdPartyLoginFilter filter = new ThirdPartyLoginFilter();
		filter.setAuthenticationSuccessHandler(authSuccessHandler());
		filter.setAuthenticationFailureHandler(authFailureHandler());
		filter.setFilterProcessesUrl("/login/thirdParty");
		filter.setAuthenticationManager(authenticationManager);
		return filter;
	}

	@Bean
	@Override
	public AuthenticationManager authenticationManager() {
		DefaultAuthenticationProvider authenticationProvider = new DefaultAuthenticationProvider(userService);
		List<AuthenticationProvider> providers = new LinkedList<>();
		providers.add(authenticationProvider);
		ProviderManager providerManager = new ProviderManager(providers);
		return providerManager;
	}

	@Bean
	public AuthenticationEntryPoint authenticationEntryPoint() {
		JsonAuthenticationEntryPoint authenticationEntryPoint = new JsonAuthenticationEntryPoint(objectMapper,
				NO_LOGIN);
		return authenticationEntryPoint;
	}

	@Bean
	public JsonAuthFailureHandler authFailureHandler() {
		JsonAuthFailureHandler jsonAuthFailureHandler = new JsonAuthFailureHandler(objectMapper);
		return jsonAuthFailureHandler;
	}

	@Bean
	public JsonAuthSuccessHandler authSuccessHandler() {
		JsonAuthSuccessHandler jsonAuthSuccessHandler = new JsonAuthSuccessHandler();
		jsonAuthSuccessHandler.setAmqpTemplate(amqpTemplate);
		return jsonAuthSuccessHandler;
	}

	@Bean
	public InvalidSessionStrategy invalidSessionStrategy() {
		return new JsonInvalidSessionStrategy(objectMapper, NO_LOGIN);
	}

	@Bean
	public CorsConfigurationSource corsConfigurationSource(){
		CorsConfiguration configuration = new CorsConfiguration();
		configuration.setAllowedOrigins(Arrays.asList("*"));
		configuration.setAllowedMethods(Arrays.asList("*"));
		configuration.setAllowedHeaders(Arrays.asList("X-Auth-Token", "Origin", "X-Requested-With", "Content-Type",
				"Accept", "User-Token", "Channel-Number","channel","appPackageId","versionNum","phoneSystem","deviceNo"
				,"activeUserId"));
		configuration.setAllowCredentials(false);
		configuration.addExposedHeader("X-Auth-Token, Location");
		UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
		corsConfigurationSource.registerCorsConfiguration("/**", configuration);
		return corsConfigurationSource;
	}

}
